// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package modfetch

import (
	"fmt"
	"io"
	"io/fs"
	"os"
	"strconv"
	"time"

	"cmd/go/internal/cfg"
	"cmd/go/internal/modfetch/codehost"
	"cmd/go/internal/par"
	"cmd/go/internal/vcs"
	web "cmd/go/internal/web"

	"golang.org/x/mod/module"
)

const traceRepo = false // 跟踪所有repo操作，用于调试

// repo表示存储单个模块的所有版本的存储库。
// 多个Goroutine同时使用必须安全。
type Repo interface {
	// ModulePath返回模块路径。
	ModulePath() string

	// Versions列出了带有给定前缀的所有已知版本。
	// 不包括伪版本。
	// 
	// 返回的版本应按semver顺序排序
	// （实现可以使用semver.Sort）。
	// 
	// Versions仅在出现问题时返回非零错误
	// 获取版本列表：如果已知匹配版本的列表
	// 以及零错误。
	// 为空，则可能返回空列表
	// 
	// 如果基础存储库不存在，
	// 版本将返回一个匹配错误的错误。是（uOS.NotExist）。
	Versions(prefix string) ([]string, error)

	// Stat返回有关修订版本的信息。
	// 修订可以是基础服务已知的任何标识符：
	// 提交哈希、分支、标记等。
	Stat(rev string) (*RevInfo, error)

	// Latest返回默认分支上的最新修订版本，
	// 在基础源代码存储库中的任何含义。
	// 仅在没有标记版本时使用。
	Latest() (*RevInfo, error)

	// GoMod返回go。给定版本的mod文件。
	GoMod(version string) (data []byte, err error)

	// Zip将给定版本的Zip文件写入dst。
	Zip(dst io.Writer, version string) error
}

// 版本描述模块存储库中的单个版本。
type RevInfo struct {
	Version string    // 此版本的建议版本字符串
	Time    time.Time // 提交时间

	// 这些字段用于任意版本的统计，
	// 但在谈论模块版本时，它们不会被记录。
	Name  string `json:"-"` // 基础存储库中的完整ID 
	Short string `json:"-"` // 缩写ID，用于伪版本
}

// Re:模块路径、导入路径、存储库根目录和查找
// 
// 模块是存储在文件树
// 中的Go包集合。mod文件位于树的根目录下。快走。mod定义模块路径，即文件树根对应的导入路径
// 。
// 该文件树中目录的导入路径是模块路径
// 与相对于根的子目录的名称连接。
// 
// 例如，路径为rsc的模块。io/qr对应于存储库中的
// 文件树https:
// 那个文件树已经成功了。mod上写着“模块rsc.io/qr”。
// 根目录中的包具有导入路径“rsc.io/qr”。
// gf256子目录中的包具有导入路径“rsc.io/qr/gf256”。
// 在本例中，“rsc.io/qr”既是模块路径又是导入路径。
// 但是“rsc.io/qr/gf256”只是一个导入路径，不是模块路径：
// 它命名了一个可导入的包，但不是模块。
// 
// 作为一种特殊情况，将模块引入之前编写的代码合并到
// 中，如果路径p使用前模块“go get”查找
// 解析到源代码存储库的根目录，则不必经过任何处理。mod file，
// 该存储库被视为有一次尝试。根目录中的mod 
// 声明模块路径p。（go.mod进一步被认为是
// 包含与任何旧版本
// 跟踪格式（如Gopkg.lock、vendor/vendor.conf等）对应的要求。）
// 
// 到目前为止的演示文稿忽略了一个事实，即源代码存储库
// 有许多不同版本的文件树，并且这些版本可能在某个特定的go是否存在差异。mod的存在及其包含的内容。
// 事实上，只有一个模块路径有一个定义良好的映射，版本
// 对-通常是编写的path@version-到特定的文件树。
// 例如rsc。木卫一/qr@v0.1.0取决于“
// repository根目录下的implicit go.mod”规则，而rsc。木卫一/qr@v0.2.0有明确的目标。摩登派青年
// 因为“go get”导入路径是rsc。io/qr和github。com/rsc/qr 
// 都重定向到Git存储库https:
// github。com/rsc/qr@v0.1.0与rsc的文件树相同。木卫一/qr@v0.1.0
// 但是不同的模块（不同的名称）。相比之下，从v0开始。2.0 
// 该存储库的。声明路径rsc的mod。io/qr，
// github。com/rsc/qr@v0.2.0是无效的模块路径、版本对。
// 在使用模块之前，导入注释也会产生相同的效果。
// 
// 与给定模块路径关联的导入路径集是
// 显然不是固定的：至少，可以始终添加具有新导入路径
// 的新目录。但另一种可能的操作是将
// 子树从模块中拆分为自己的模块。如果仔细操作，
// 可以在保持客户端兼容性的同时执行此操作。
// 例如，假设我们想要拆分rsc。io/qr/gf256转换成
// 自己的模块，这样就有了两个模块rsc。io/qr和rsc。io/qr/gf256。
// 那么我们可以同时发行rsc。io/qr v0。3.0（删除gf256子目录）
// 和rsc。io/qr/gf256 v0。1.0，包括各自的围棋。mod 
// 相互指向的循环需求：rsc。io/qr v0。3.0需要
// rsc。io/qr/gf256 v0。1.0，反之亦然。然后一个构建可以是
// 使用一个旧的rsc。io/qr模块，包括gf256软件包，但如果
// 则会在较新的rsc上增加要求。io/qr或更新的
// rsc。io/qr/gf256模块，它将自动在互补的一半上添加
// 要求，确保rsc。io/qr/gf256是
// 可由构建导入，并且它仅由单个模块定义
// 。gf256软件包可以通过另一个同时发布的rsc版本移回
// 原版。io/qr v0。4.0包括
// gf256子目录和一个rsc。io/qr/gf256 v0。2.0，根目录中没有代码
// 以及新的需求周期。
// 以这种方式改变模块边界的能力在大规模程序重构中很重要，类似于中所述的
// 功能https:
// 
// 改变模块边界的可能性再次强调了
// 您必须知道模块路径及其版本
// 才能确定该模块直接提供的包集。
// 
// 最重要的是，单个代码库
// 可以包含多个模块，无论是在分支目录还是子目录中，
// 作为一种有限的monorepo。例如rsc。io/qr/v2，
// v2。x、 x rsc的延续。io/qr，预计将在中的v2中找到
// https:
// 在根目录或v2子目录中，通过go消除歧义。摩登派青年
// 同样，与模块
// 对应的精确文件树取决于我们正在考虑的版本。
// 
// 基础存储库也可能随时间而改变，
// 而不改变模块路径。如果我通过
// 将github回购协议复制到https:
// 那么所有版本的客户端都应该开始从bitbucket而不是github获取数据。也就是说，与确切的文件树相反，
// 与模块路径
// 关联的源代码存储库的位置不取决于模块版本。（这是出于设计，因为这些重定向的目的是允许包作者建立一个稳定的
// 名称，可以随着代码从一个服务移动到另一个服务而更新。）
// 
// 所有这些都是在这个
// 文件中定义的查找API的重要背景。
// 
// 查找函数采用模块路径，并返回代表该模块路径的Repo。仅在路径上，查找只能起到一点作用。
// 它可以检查路径的格式是否正确（参见semver.CheckPath）
// 并且可以检查路径是否可以解析为目标存储库。
// 为了避免版本控制访问，除非绝对必要，
// 查找不会尝试连接到存储库本身。

var lookupCache par.Cache

type lookupCacheKey struct {
	proxy, path string
}

// Lookup返回具有给定模块路径的模块，
// 通过给定代理获取。
// 
// 可分辨代理“direct”表示应该获取路径
// 从其来源，而“noproxy”表示只有在GONOPROXY与给定路径匹配时，才应直接获取补丁。
// 
// 对于可分辨代理“off”，Lookup始终返回一个Repo，该Repo为每个方法调用返回一个非零错误。
// 
// 成功返回并不保证模块
// 具有任何已定义的版本。
func Lookup(proxy, path string) Repo {
	if traceRepo {
		defer logCall("Lookup(%q, %q)", proxy, path)()
	}

	type cached struct {
		r Repo
	}
	c := lookupCache.Do(lookupCacheKey{proxy, path}, func() any {
		r := newCachingRepo(path, func() (Repo, error) {
			r, err := lookup(proxy, path)
			if err == nil && traceRepo {
				r = newLoggingRepo(r)
			}
			return r, err
		})
		return cached{r}
	}).(cached)

	return c.r
}

// lookup返回具有给定模块路径的模块。
func lookup(proxy, path string) (r Repo, err error) {
	if cfg.BuildMod == "vendor" {
		return nil, errLookupDisabled
	}

	if module.MatchPrefixPatterns(cfg.GONOPROXY, path) {
		switch proxy {
		case "noproxy", "direct":
			return lookupDirect(path)
		default:
			return nil, errNoproxy
		}
	}

	switch proxy {
	case "off":
		return errRepo{path, errProxyOff}, nil
	case "direct":
		return lookupDirect(path)
	case "noproxy":
		return nil, errUseProxy
	default:
		return newProxyRepo(proxy, path)
	}
}

type lookupDisabledError struct{}

func (lookupDisabledError) Error() string {
	if cfg.BuildModReason == "" {
		return fmt.Sprintf("module lookup disabled by -mod=%s", cfg.BuildMod)
	}
	return fmt.Sprintf("module lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason)
}

var errLookupDisabled error = lookupDisabledError{}

var (
	errProxyOff       = notExistErrorf("module lookup disabled by GOPROXY=off")
	errNoproxy  error = notExistErrorf("disabled by GOPRIVATE/GONOPROXY")
	errUseProxy error = notExistErrorf("path does not match GOPRIVATE/GONOPROXY")
)

func lookupDirect(path string) (Repo, error) {
	security := web.SecureOnly

	if module.MatchPrefixPatterns(cfg.GOINSECURE, path) {
		security = web.Insecure
	}
	rr, err := vcs.RepoRootForImportPath(path, vcs.PreferMod, security)
	if err != nil {
		// 我们不知道在哪里可以找到具有此路径的模块的代码。
		return nil, notExistError{err: err}
	}

	if rr.VCS.Name == "mod" {
		// 使用基本URL rr从代理获取模块。回购协议。
		return newProxyRepo(rr.Repo, path)
	}

	code, err := lookupCodeRepo(rr)
	if err != nil {
		return nil, err
	}
	return newCodeRepo(code, rr.Root, path)
}

func lookupCodeRepo(rr *vcs.RepoRoot) (codehost.Repo, error) {
	code, err := codehost.NewRepo(rr.VCS.Cmd, rr.Repo)
	if err != nil {
		if _, ok := err.(*codehost.VCSError); ok {
			return nil, err
		}
		return nil, fmt.Errorf("lookup %s: %v", rr.Root, err)
	}
	return code, nil
}

// loggingRepo是一个围绕基础Repo的包装器
// ，它在每次调用的开始和结束时打印一条日志消息。
// 调试时可以插入。
type loggingRepo struct {
	r Repo
}

func newLoggingRepo(r Repo) *loggingRepo {
	return &loggingRepo{r}
}

// logCall使用格式和参数打印日志消息，然后
// 还返回一个函数，该函数将再次打印同一消息，以及经过的时间。
// 典型用法是：
// 
// 延迟日志调用（“hello%s”，arg）（）
// 
// 注意最后的（）。
func logCall(format string, args ...any) func() {
	start := time.Now()
	fmt.Fprintf(os.Stderr, "+++ %s\n", fmt.Sprintf(format, args...))
	return func() {
		fmt.Fprintf(os.Stderr, "%.3fs %s\n", time.Since(start).Seconds(), fmt.Sprintf(format, args...))
	}
}

func (l *loggingRepo) ModulePath() string {
	return l.r.ModulePath()
}

func (l *loggingRepo) Versions(prefix string) (tags []string, err error) {
	defer logCall("Repo[%s]: Versions(%q)", l.r.ModulePath(), prefix)()
	return l.r.Versions(prefix)
}

func (l *loggingRepo) Stat(rev string) (*RevInfo, error) {
	defer logCall("Repo[%s]: Stat(%q)", l.r.ModulePath(), rev)()
	return l.r.Stat(rev)
}

func (l *loggingRepo) Latest() (*RevInfo, error) {
	defer logCall("Repo[%s]: Latest()", l.r.ModulePath())()
	return l.r.Latest()
}

func (l *loggingRepo) GoMod(version string) ([]byte, error) {
	defer logCall("Repo[%s]: GoMod(%q)", l.r.ModulePath(), version)()
	return l.r.GoMod(version)
}

func (l *loggingRepo) Zip(dst io.Writer, version string) error {
	dstName := "_"
	if dst, ok := dst.(interface{ Name() string }); ok {
		dstName = strconv.Quote(dst.Name())
	}
	defer logCall("Repo[%s]: Zip(%s, %q)", l.r.ModulePath(), dstName, version)()
	return l.r.Zip(dst, version)
}

// errRepo是一种对所有操作返回相同错误的Repo。
// 
// 它与缓存结合使用非常有用，因为缓存命中不会尝试
// 禁止的操作。
type errRepo struct {
	modulePath string
	err        error
}

func (r errRepo) ModulePath() string { return r.modulePath }

func (r errRepo) Versions(prefix string) (tags []string, err error) { return nil, r.err }
func (r errRepo) Stat(rev string) (*RevInfo, error)                 { return nil, r.err }
func (r errRepo) Latest() (*RevInfo, error)                         { return nil, r.err }
func (r errRepo) GoMod(version string) ([]byte, error)              { return nil, r.err }
func (r errRepo) Zip(dst io.Writer, version string) error           { return r.err }

// notexist错误就像fs。ErrNotExist，但带有自定义消息
type notExistError struct {
	err error
}

func notExistErrorf(format string, args ...any) error {
	return notExistError{fmt.Errorf(format, args...)}
}

func (e notExistError) Error() string {
	return e.err.Error()
}

func (notExistError) Is(target error) bool {
	return target == fs.ErrNotExist
}

func (e notExistError) Unwrap() error {
	return e.err
}
